//
//  NSObject+XYAdd.m
//  面试总结
//
//  Created by lxy on 2017/11/21.
//  Copyright © 2017年 lxy. All rights reserved.
//

/*
 performSelector: withObject:
 是在iOS中的一种方法调用方式。他可以向一个对象传递任何消息，而不需要在编译的时候声明这些方法。所以这也是runtime的一种应用方式,performSelector最后也还是用objc_msgSend这个方法进行消息转发的。
 所以performSelector和直接调用方法的区别就在与runtime。直接调用编译是会自动校验。如果方法不存在，那么直接调用 在编译时候就能够发现，编译器会直接报错。
 但是使用performSelector的话一定是在运行时候才能发现，如果此方法不存在就会崩溃。所以如果使用performSelector的话他就会有个最佳伴侣- (BOOL)respondsToSelector:(SEL)aSelector;来在运行时判断对象是否响应此方法。
 
 */

/*
 NSProxy:不继承自NSObject的官方类据我所知只有NSProxy，NSProxy主要目的是用forwardInvocation:方法来进行消息转发，如果继承NSObject容易导致冲突，所以你看看NSProxy的API就会发现相比NSObject来说太简洁了。
 */


/*
 在 iOS中可以直接调用某个对象的消息方式有两种：
 一种是performSelector:withObject；
 再一种就是NSInvocation。
 第一种方式比较简单，能完成简单的调用。但是对于>2个的参数或者有返回值的处理，那performSelector:withObject就显得有点有心无力了，那么在这种情况下，我们就可以使用NSInvocation来进行这些相对复杂的操作
 */

#import "NSObject+XYAdd.h"

@implementation NSObject (XYAdd)

- (id)performSelector:(SEL)aSelector withObjects:(NSArray *)objects {
    if (aSelector == nil) return nil;

    //方法签名（方法名、方法参数、方法返回值）
    NSMethodSignature *methodSignature = [self methodSignatureForSelector:aSelector];
    if (!methodSignature) {
        // NSException 处理异常
        NSException *exception = [NSException exceptionWithName:[NSString stringWithFormat:@"异常--方法找不到:%@",NSStringFromSelector(aSelector)] reason:@"方法调用出现异常" userInfo:nil];
        @throw exception;
    }
    
     // NSInvocation : 利用一个NSInvocation对象包装一次方法调用（方法调用者、方法名、方法参数、方法返回值）
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
    //设置方法调用者
    invocation.target = self;
    //设置方法名称
    invocation.selector = aSelector;
    
    // 设置参数，这里的Index要从2开始，以为0跟1已经被占据了，分别是self（target）,selector(_cmd)
    // 此处不能通过遍历参数数组objects.count来设置参数，因为外界传进来的参数个数是不可控的
    // 因此通过numberOfArguments方法获取的参数个数,是包含self和_cmd的，然后比较方法需要的参数和外界传进来的参数个数，并且取它们之间的最小值
    NSInteger paramsCount = methodSignature.numberOfArguments - 2;
    paramsCount = MIN(paramsCount, objects.count);
    
    for (NSInteger i = 0; i<paramsCount; i++) {
        id object = objects[i];
        // 判断需要设置的参数是否是NSNull, 如果是就设置为nil
        if ([object isKindOfClass:[NSNull class]]) {
            object = nil;
        }
        [invocation setArgument:&object atIndex:i+2];
    }
    
    // 调用方法
    [invocation invoke];
    
    // 获取返回值
    id returnValue = nil;
    // 有返回值类型，才去获得返回值
    if (methodSignature.methodReturnLength) {
        [invocation getReturnValue:&returnValue];
    }
    
    return returnValue;
}

@end
